package mop.xml;

import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import mop.exception.IllegalIdException;

import mop.model.World;
import mop.model.resource.ResourceType;
import mop.model.task.field.Field;
import mop.model.task.field.IntField;
import mop.model.task.field.StringField;
import mop.model.task.tasktype.TaskType;
import mop.model.user.UserType;
import mop.util.Couple;

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

/**
 * XML reader (SAX) for the theme files.
 * 
 * @author 	MOP Team 7
 */
public class ThemeXMLReader extends DefaultHandler
{
    public static void main(String args[])
    {
            World world = new World();
            ThemeXMLReader handler = new ThemeXMLReader(world);
            File xmlFile = new File("theme_development_1.xml");
            handler.process(xmlFile);
            System.out.println("\nAll done!");
    }

    // debug mode on = true
    private final boolean DEBUG_MODE = false;

    // reference to World object
    private World world;

    // parser object
    private SAXParser parser;

    /**
     * Constructor for this SAX parser.
     *
     * Initializes the parser object and the "tags" hashmap
     * containing the booleans indicating which elements are being read.
     */
    public ThemeXMLReader(World world)
    {
            this.world = world;
            this.parser = null;
            this.tags = new HashMap<String, Boolean>();

            this.lastAttr = new HashMap<String, String>();
            this.taskTypes = new LinkedList<TaskTypeXML>();
            this.userTypes = new LinkedList<UserType>();
            this.resourceTypes = new LinkedList<ResourceType>();
    }

    /**
     * Starts the processing of an XML file.
     *
     * @param 	file
     * 			An XML file as a File object.
     */
    public void process(File file)
    {
        SAXParserFactory spf = SAXParserFactory.newInstance();
        spf.setNamespaceAware(true);
        spf.setValidating(true);

        // Make SAX parser

        try {
            parser = spf.newSAXParser();
        } catch (SAXException e) {
            e.printStackTrace(System.err);
            System.exit(1);
        } catch (ParserConfigurationException e) {
            e.printStackTrace(System.err);
            System.exit(1);
        }

        // Start parsing

        try {
            parser.parse(file, this);
        } catch (IOException e) {
            e.printStackTrace(System.err);
        } catch (SAXException e) {
            e.printStackTrace(System.err);
        }

        // Done parsing - add rest

        for (TaskTypeXML ttx : this.taskTypes)
        {
            // Make hashmap of requirements
            HashMap<ResourceType,Couple<Integer,Integer>> reqs = new HashMap<ResourceType,Couple<Integer,Integer>>();
            for (String type : ttx.necessities.keySet())
            {
                try {
                    ResourceType r = world.getResourceTypeDataBase().find(type);
                    reqs.put(r, ttx.necessities.get(type));
                } catch (IllegalIdException ex) {
                    Logger.getLogger(ThemeXMLReader.class.getName()).log(Level.SEVERE, null, ex);
                }
            }

            // Make list of ownertypes
            List<UserType> ownertypes = new LinkedList<UserType>();
            for (String type : ttx.ownertypes)
            {
                try {
                    ownertypes.add(world.getUserTypeDataBase().find(type));
                } catch (IllegalIdException ex) {
                    Logger.getLogger(ThemeXMLReader.class.getName()).log(Level.SEVERE, null, ex);
                }
            }

            // Make TaskType
            try {
                TaskType t = new TaskType(world.getTaskTypeDataBase(), ttx.description, ttx.fields, reqs, ownertypes);
            } catch (Throwable t) {
                System.out.println("An exception occurred while reading XML!\n" + t.toString());
                t.printStackTrace(System.err);
            }
        }
    }

    /**
     * @see		DefaultHandler
     */
    public void startDocument()
    {
    }

    /**
     * @see		DefaultHandler
     */
    public void endDocument()
    {
    }

    // Booleans indicating which tag is "active".
    private HashMap<String, Boolean> tags;

    /**
     * Returns whether the given element (String) is active or not.
     *
     * @param 	el
     * 			A string representing an XML element.
     * @return	True if the tag was recently read (and the end tag not yet), else false.
     */
    private boolean isElementActive(String el)
    {
            return (tags.get(el) != null && tags.get(el));
    }

    /**
     * @see		DefaultHandler
     */
    public void startElement(String uri, String localName, String qname, Attributes attr)
    {
        // Set tag active
        tags.put(localName, true);

        // Save attributes
        if (attr != null && attr.getLength() > 0) {
            for (int i=0; i<attr.getLength(); i++) {
                this.lastAttr.put(attr.getLocalName(i), attr.getValue(i));
            }
        }

        // TaskType?
        if (localName.equals("taskType"))
        {
            // @TODO add id
            this.taskTypes.add(new TaskTypeXML());
            lastTaskType().description = attr.getValue("name");
            lastTaskType().fields = new LinkedList<Field>();
            lastTaskType().necessities = new HashMap<String,Couple<Integer,Integer>>();
            lastTaskType().ownertypes = new LinkedList<String>();
        }

        // Field?
        if (localName.equals("field"))
        {
            Field f = null;
            if (this.lastAttr.get("nature").equals("textual"))
            {
                try {
                    f = new StringField(this.lastAttr.get("id"), this.lastAttr.get("name"));
                } catch (Throwable t) {
                    System.out.println("An exception occurred while reading XML!\n" + t.toString());
                    t.printStackTrace(System.err);
                }
            } else if (this.lastAttr.get("nature").equals("textual")) {
                try {
                    f = new IntField(this.lastAttr.get("id"), this.lastAttr.get("name"));
                } catch (Throwable t) {
                    System.out.println("An exception occurred while reading XML!\n" + t.toString());
                    t.printStackTrace(System.err);
                }
            }

            lastTaskType().fields.add(f);
        }

        // Requirement?
        if (localName.equals("requirement"))
        {
            int min = 0;
            if (!attr.getValue("min").equals("*")) min = Integer.parseInt(attr.getValue("min"));

            int max = 0;
            if (!attr.getValue("max").equals("*")) max = Integer.parseInt(attr.getValue("max"));

            Couple<Integer,Integer> minmax = new Couple<Integer,Integer>(min,max);

            lastTaskType().necessities.put(attr.getValue("type"), minmax);
        }

        // Owner?
        if (localName.equals("owner"))
        {
            lastTaskType().ownertypes.add(attr.getValue("type"));
        }

        // userType?
        if (localName.equals("userType"))
        {
            try {
                this.userTypes.add(new UserType(world.getUserTypeDataBase(), attr.getValue("id"), attr.getValue("name")));
            } catch (Throwable t) {
                System.out.println("An exception occurred while reading XML!\n" + t.toString());
                t.printStackTrace(System.err);
            }
        }

        // resourceType?
        if (localName.equals("resourceType"))
        {
            try {
                this.resourceTypes.add(new ResourceType(world.getResourceTypeDataBase(), attr.getValue("id"), attr.getValue("name")));
            } catch (Throwable t) {
                System.out.println("An exception occurred while reading XML!\n" + t.toString());
                t.printStackTrace(System.err);
            }
        }
    }

    /**
     * @see		DefaultHandler
     */
    public void endElement(String uri, String localName, String qname)
    {
        // Set tag inactive
        tags.put(localName, false);
    }

    /**
     * @see		DefaultHandler
     */
    public void characters(char[] ch, int start, int length)
    {
        String s = new String(ch,start,length).trim();
        // niet nodig eigenlijk
    }

    /**
     * @return  TaskTypeXML
     */
    private TaskTypeXML lastTaskType()
    {
        return this.taskTypes.get(this.taskTypes.size() - 1);
    }

    // this is all for remembering stuff

    private HashMap<String, String> lastAttr;
    private List<TaskTypeXML> taskTypes;
    private List<UserType> userTypes;
    private List<ResourceType> resourceTypes;

    private class TaskTypeXML {
        public TaskTypeXML() {}
        public String id;
        public String description;
        public List<Field> fields;
        public HashMap<String,Couple<Integer,Integer>> necessities;
        public List<String> ownertypes;
    }
}